/*
Copyright 2008, 2009 Joonas Lehtolahti

This file is part of Openfire-C.

Openfire-C is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Openfire-C is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License
along with Openfire-C.  If not, see <http://www.gnu.org/licenses/>.
*/

/* These are helper functions to make it easier to handle the structures */

#include "../shared/n0sha1.h"
#include "XfirePacket.h"
#include "XfirePacketHelper.h"
#include <string.h>
#include <stdio.h>

/*
 * XfireAttributeString (non0string *name, non0string* content)
 *   Allocates space for attribute of type string.
 * 
 * Parameters:
 *   name      the name of the attribute
 *   content   the attribute contents
 * 
 * Returns:
 *   struct XfirePattr with the data
 * 
 * Note:
 *   caller must free the structure afterwards.
 *   When used inside a packet, using XfirePacketFree will also free this data!
 */
DLLEXPORT(struct XfirePattr*) XfireAttributeString(non0string *name, non0string* content) {
	struct XfirePattr *a = (struct XfirePattr *)dmalloc(sizeof(struct XfirePattr));
	a->aid = XFP_TYPE_STRING;
	a->attr = content;
	a->name = name;
	return a;
}

/*
 * XfireAttributeInt (non0string *name, unsigned int content)
 *   Allocates space for attribute of type int
 * 
 * Parameters:
 *   name      the name of the attribute
 *   content   the attribute contents
 * 
 * Returns:
 *   struct XfirePattr with the data
 * 
 * Note:
 *   caller must free the structure afterwards.
 *   When used inside a packet, using XfirePacketFree will also free this data!
 */
DLLEXPORT(struct XfirePattr*) XfireAttributeInt(non0string *name, unsigned int content) {
	struct XfirePattr *a = (struct XfirePattr *)dmalloc(sizeof(struct XfirePattr));
	unsigned int *x = (unsigned int*)dmalloc(sizeof(int));
	*x = content;
	a->aid = XFP_TYPE_INT;
	a->attr = x;
	a->name = name;
	return a;
}

/*
 * XfireAttributeBool (non0string *name, unsigned int content)
 *   Allocates space for attribute of type boolean
 * 
 * Parameters:
 *   name      the name of the attribute
 *   content   the attribute contents
 * 
 * Returns:
 *   struct XfirePattr with the data
 * 
 * Note:
 *   caller must free the structure afterwards.
 *   When used inside a packet, using XfirePacketFree will also free this data!
 */
DLLEXPORT(struct XfirePattr*) XfireAttributeBool(non0string *name, unsigned int content) {
	struct XfirePattr *a = (struct XfirePattr *)dmalloc(sizeof(struct XfirePattr));
	unsigned int *x = (unsigned int*)dmalloc(sizeof(int));
	*x = content;
	a->aid = XFP_TYPE_BOOLEAN;
	a->attr = x;
	a->name = name;
	return a;
}

/*
 * XfireAttributeLong (non0string *name, unsigned long long int content)
 *   Allocates space for attribute of type long
 * 
 * Parameters:
 *   name      the name of the attribute
 *   content   the attribute contents
 * 
 * Returns:
 *   struct XfirePattr with the data
 * 
 * Note:
 *   caller must free the structure afterwards.
 *   When used inside a packet, using XfirePacketFree will also free this data!
 */
DLLEXPORT(struct XfirePattr*) XfireAttributeLong(non0string *name, unsigned long long int content) {
	struct XfirePattr *a = (struct XfirePattr *)dmalloc(sizeof(struct XfirePattr));
	unsigned long long int *x = (unsigned long long int*)dmalloc(sizeof(unsigned long long int));
	*x = content;
	a->aid = XFP_TYPE_LONG;
	a->attr = x;
	a->name = name;
	return a;
}

/*
 * XfireAttributeSid (non0string *name, non0string *content)
 *   Allocates space for attribute of type SID
 * 
 * Parameters:
 *   name      the name of the attribute
 *   content   the attribute contents
 * 
 * Returns:
 *   struct XfirePattr with the data
 * 
 * Note:
 *   caller must free the structure afterwards.
 *   When used inside a packet, using XfirePacketFree will also free this data!
 */
DLLEXPORT(struct XfirePattr*) XfireAttributeSid(non0string *name, non0string *content) {
	struct XfirePattr *a = (struct XfirePattr *)dmalloc(sizeof(struct XfirePattr));
	a->aid = XFP_TYPE_SID;
	a->attr = content;
	a->name = name;
	return a;
}

/*
 * XfireAttributeGcid (non0string *name, non0string *content)
 *   Allocates space for attribute of type Group Chat ID
 * 
 * Parameters:
 *   name      the name of the attribute
 *   content   the attribute contents
 * 
 * Returns:
 *   struct XfirePattr with the data
 * 
 * Note:
 *   caller must free the structure afterwards.
 *   When used inside a packet, using XfirePacketFree will also free this data!
 */
DLLEXPORT(struct XfirePattr*) XfireAttributeGcid(non0string *name, non0string *content) {
	struct XfirePattr *a = (struct XfirePattr *)dmalloc(sizeof(struct XfirePattr));
	a->aid = XFP_TYPE_GCID;
	a->attr = content;
	a->name = name;
	return a;
}

/*
 * XfireAttributeList (non0string *name, struct XfirePattr4 *content)
 *   Allocates space for attribute of type list
 * 
 * Parameters:
 *   name      the name of the attribute
 *   content   the attribute contents
 * 
 * Returns:
 *   struct XfirePattr with the data
 * 
 * Note:
 *   caller must free the structure afterwards.
 *   When used inside a packet, using XfirePacketFree will also free this data!
 */
DLLEXPORT(struct XfirePattr*) XfireAttributeList(non0string *name, struct XfirePattr4 *content) {
	struct XfirePattr *a = (struct XfirePattr *)dmalloc(sizeof(struct XfirePattr));
	a->aid = XFP_TYPE_LIST;
	a->attr = content;
	a->name = name;
	return a;
}

/*
 * XfireAttributePacket (non0string *name, struct XfirePacket *content)
 *   Allocates space for attribute of type packet (also npacket)
 * 
 * Parameters:
 *   name      the name of the attribute
 *   content   the attribute contents
 * 
 * Returns:
 *   struct XfirePattr with the data
 * 
 * Note:
 *   caller must free the structure afterwards.
 *   When used inside a packet, using XfirePacketFree will also free this data!
 */
DLLEXPORT(struct XfirePattr*) XfireAttributePacket(non0string *name, struct XfirePacket *content) {
	struct XfirePattr *a = (struct XfirePattr *)dmalloc(sizeof(struct XfirePattr));
	if (content->shortnames)
		a->aid = XFP_TYPE_NPACKET;
	else
		a->aid = XFP_TYPE_PACKET;
	a->attr = content;
	a->name = name;
	return a;
}

/*
 * XfirePacketSetAttribute (struct XfirePacket *packet, unsigned int index, struct XfirePattr *attribute)
 *   Sets the indexed attribute of the specified packet
 * 
 * Parameters:
 *   packet      the packet to be modified
 *   index       attribute index in the packet
 *   attribute   the attribute to be set into the packet
 * 
 * Returns:
 *   0 if the operation failed
 *   1 if the operation succeeded
 */
DLLEXPORT(int) XfirePacketSetAttribute(struct XfirePacket *packet, unsigned int index, struct XfirePattr *attribute) {
	if (!packet || !attribute) return 0;
	if (index > packet->attrs) return 0;
	packet->attributes[index] = attribute;
	return 1;
}



/*
 * XfireRecv (int sock, non0string **data, unsigned char *recvbuffer, size_t *current_buffer_length)
 *   Receives network data on socket into non0string, slicing as Xfire packets
 * 
 * Parameters:
 *   sock        the network socket to recv on
 *   data        pointer (out) to pointer to non0string struct where the data is to be read into.
 *               the struct is automatically allocated to correct size when packet is received fully,
 *               the calling procedure must free the data with n0sfree(*data) afterwards.
 *   recvbuffer  the buffer for incoming data, must be of size XFP_PACKET_MAXSIZE or larger
 *   current_buffer_length
 *               information on how much data exists on the buffer
 * 
 * Returns:
 *  -1 if there was a network error
 *   0 if the connection was closed
 *   1 if there is no additional data in buffer
 *   2 if there is more data in buffer, call the function again
 * 
 * Notes:
 *   The calling procedure must allocate (and finally free) memory for recvbuffer,
 *   the size being at least XFP_PACKET_MAXSIZE. current_buffer_length must be 0 initially.
 *   This function will edit the contents of recvbuffer and current_buffer_length as they are to
 *   be used in all subsequent calls to this function, so the calling procedure should retain
 *   them for as long as it will use Xfire connectivity without editing or freeing them.
 *   The **data will point to NULL if there was no completed packet, otherwise it will be allocated
 *   to hold the complete packet and the calling procedure must free it.
 */
DLLEXPORT(int) XfireRecv(int sock, non0string **data, unsigned char *recvbuffer, size_t *current_buffer_length) {
	int recvret = 0;
	size_t length;
	non0string *temp;
	*data = NULL; /* default value indicating no packet received */
	if (*current_buffer_length < XFP_SIZE_PACKETLENGTH) {
		recvret = recv(sock, &recvbuffer[*current_buffer_length], XFP_PACKET_MAXSIZE-*current_buffer_length, 0);
		if (recvret < 1) {
			/* An error occured or the connection was closed */
			return recvret;
		}
		*current_buffer_length += (size_t)recvret;
	}
	if (*current_buffer_length >= XFP_SIZE_PACKETLENGTH) {
		length = *((size_t*)recvbuffer) & XFP_MASK_PACKETLENGTH;
		if (*current_buffer_length < length && recvret == 0) {
			/* needs more data, but if recv() was already called,
			   do not call again to not lock up the thread in case
			   network is busy and cannot supply more data, rather
			   wait for select() to indicate for further data and then
			   call this function again. */
			recvret = recv(sock, &recvbuffer[*current_buffer_length], XFP_PACKET_MAXSIZE-*current_buffer_length, 0);
			if (recvret < 1) {
				/* An error occured or the connection was closed */
				return recvret;
			}
			*current_buffer_length += (size_t)recvret;
		}
		if (*current_buffer_length >= length) {
			/* we have received a whole packet */
			*data = n0salloc(length);
			memcpy((*data)->s, recvbuffer, length);
			*current_buffer_length -= length;
			if (*current_buffer_length > 0) {
				memmove(recvbuffer, &recvbuffer[length], *current_buffer_length);
				if (*current_buffer_length >= XFP_SIZE_PACKETLENGTH) {
					length = *((size_t*)recvbuffer) & XFP_MASK_PACKETLENGTH;
					if (*current_buffer_length >= length) {
						/* indicate more packets in buffer */
						return 2;
					}
				}
			}
		}
	}
	return 1;
}

/*
 * XfireSend (int sock, non0string *data, size_t completed)
 *   Sends network data on socket from non0string, starting from position completed
 * 
 * Parameters:
 *   sock        the network socket to send on
 *   data        pointer to non0string that holds the data to be sent
 *   completed   number of bytes completed so far
 * 
 * Returns:
 *   -  the number of bytes completed after operation, if return value == data->l, the packet has been
 *        sent completely, otherwise subsequent calls can be made by using the return value as the
 *        completed parameter and using the same data pointer.
 *   -  0 if the connection was closed
 *   - -1 if there was a network error
 */
DLLEXPORT(int) XfireSend(int sock, non0string *data, size_t completed) {
	int retval = send(sock, data->s+completed, data->l-completed, 0);
	if (retval < 1) {
		return retval;
	}
	return completed + retval;
}

/*
 * XfirePacketGetAttributeByName (struct XfirePacket *packet, non0string *name)
 *   Loops through the attributes inside the packet and looks for attribute with the same name
 *   as passed in parameters.
 * 
 * Parameters:
 *   packet   
 *   name     
 * 
 * Returns:
 *   Pointer to the attribute if found, NULL if not found
 */
DLLEXPORT(struct XfirePattr*) XfirePacketGetAttributeByName(struct XfirePacket *packet, non0string *name) {
	unsigned int ai = 0;
	struct XfirePattr *a;
	
	if (!packet) return NULL;
	
	while (ai < packet->attrs) {
		a = (struct XfirePattr *)packet->attributes[ai];
		if (!n0strcmp(a->name, name)) return a;
		ai++;
	}
	return NULL;
}

/*
 * XfireStringPairsSplit (non0string *pairs)
 *   Splits a stringPairs string into linked lists
 * 
 * Parameters:
 *   pairs  the string to be split
 * 
 * Returns:
 *   A pointer to a PairedList structure or NULL for errors,
 *   the items in the list are of type non0string[2] (not pointers!)
 * 
 * Notes:
 *   The calling function must free both the LinkedLists as well as
 *   the PairedList structure!
 */
DLLEXPORT(struct PairedList*) XfireStringPairsSplit(non0string *pairs) {
	size_t length;
	unsigned char *walker = pairs->s, *begin = pairs->s, *final = (pairs->s+pairs->l);
	struct PairedList *newpl;
	non0string *name, *value;
	
	if (!pairs) return NULL;
	
	newpl = (struct PairedList *)dmalloc(sizeof(struct PairedList));
	newpl->names = LLcreate();
	newpl->values = LLcreate();
	
	while (walker != final) {
		if (*walker == 0x01) {
			length = walker-begin;
			name = n0salloc(length);
			memcpy(name->s, begin, length);
			begin = walker+1;
		}
		else if (*walker == 0x02) {
			length = walker-begin;
			value = n0salloc(length);
			memcpy(value->s, begin, length);
			begin = walker+1;
			LLaddLast(newpl->names, name);
			LLaddLast(newpl->values, value);
		}
		walker++;
	}
	
	return newpl;
}

/*
 * XfirePacketGetListAttribute (struct XfirePacket *packet, const char *name)
 *   Converts a list type attribute into LinkedList structure
 * 
 * Parameters:
 *   packet  the packet to search the attribute from
 *   name    the name of the attribute
 * 
 * Returns:
 *   Pointer to allocated LinkedList structure or NULL if attr was not a list
 */
DLLEXPORT(LinkedList*) XfirePacketGetListAttribute(struct XfirePacket *packet, unsigned const char *name) {
	struct XfirePattr4 *alist;
	unsigned int i=0;
	LinkedList *newll;
	
	non0string *finder = char2non(name);
	struct XfirePattr *attr = XfirePacketGetAttributeByName(packet, finder);
	n0sfree(finder);
	
	if (!attr) return NULL;
	
	if (attr->aid != XFP_TYPE_LIST) return NULL;
	
	newll = LLcreate();
	alist = (struct XfirePattr4 *)(attr->attr);
	while (i<alist->size) {
		LLaddLast(newll, alist->attr[i++]);
	}
	
	return newll;
}

/*
 * XfirePacketGetStringAttribute (struct XfirePacket *packet, const char *name)
 *   Returns a string type attribute as non0string
 * 
 * Parameters:
 *   packet  the packet to search the attribute from
 *   name    the name of the attribute
 * 
 * Returns:
 *   Pointer to the non0string structure or NULL if not string
 */
DLLEXPORT(non0string*) XfirePacketGetStringAttribute(struct XfirePacket *packet, unsigned const char *name) {
	non0string *finder = char2non(name);
	struct XfirePattr *attr = XfirePacketGetAttributeByName(packet, finder);
	n0sfree(finder);
	
	if (!attr) return NULL;
	
	if (attr->aid != XFP_TYPE_STRING && attr->aid != XFP_TYPE_SID && attr->aid != XFP_TYPE_GCID) return NULL;
	
	return (non0string*)attr->attr;
}

/*
 * XfirePacketGetPacketAttribute (struct XfirePacket *packet, const char *name)
 *   Returns a packet type attribute as struct XfirePacket
 * 
 * Parameters:
 *   packet  the packet to search the attribute from
 *   name    the name of the attribute
 * 
 * Returns:
 *   Pointer to the XfirePacket structure or NULL if not a packet
 */
DLLEXPORT(struct XfirePacket*) XfirePacketGetPacketAttribute(struct XfirePacket *packet, unsigned const char *name) {
	non0string *finder = char2non(name);
	struct XfirePattr *attr = XfirePacketGetAttributeByName(packet, finder);
	n0sfree(finder);
	
	if (!attr) return NULL;
	
	if (attr->aid != XFP_TYPE_PACKET && attr->aid != XFP_TYPE_NPACKET) return NULL;
	
	return (struct XfirePacket*)attr->attr;
}

/*
 * XfirePacketGetIntegerAttribute (struct XfirePacket *packet, const char *name)
 *   Returns an integer type attribute as unsigned int
 * 
 * Parameters:
 *   packet  the packet to search the attribute from
 *   name    the name of the attribute
 * 
 * Returns:
 *   Pointer to the integer or NULL if not integer
 */
DLLEXPORT(unsigned int*) XfirePacketGetIntegerAttribute(struct XfirePacket *packet, unsigned const char *name) {
	non0string *finder = char2non(name);
	struct XfirePattr *attr = XfirePacketGetAttributeByName(packet, finder);
	n0sfree(finder);
	
	if (!attr) return NULL;
	
	if (attr->aid != XFP_TYPE_INT && attr->aid != XFP_TYPE_BOOLEAN) return NULL;
	
	return (unsigned int*)attr->attr;
}


/*
 * XfirePacketCreateListAttribute (LinkedList *list, unsigned int aid)
 *   Converts a LinkedList structure into Xfire list attribute type
 * 
 * Parameters:
 *   list   the list to convert
 *   aid    data type
 * 
 * Returns:
 *   Pointer to allocated XfirePattr4 structure or NULL
 */
DLLEXPORT(struct XfirePattr4*) XfirePacketCreateListAttribute(LinkedList *list, unsigned int aid) {
	if (list) {
		struct XfirePattr4 *alist = (struct XfirePattr4*)dmalloc(sizeof(struct XfirePattr4));
		ListNode *node = list->first;
		unsigned int index = 0;
		alist->aid = aid;
		alist->size = list->nodes;
		alist->attr = (void**)dmalloc(alist->size*sizeof(void*));
		while (node) {
			alist->attr[index++] = node->x;
			node = node->next;
		}
		
		return alist;
	}
	return NULL;
}


DLLEXPORT(int) XfSendAll(int s, non0string *data) {
	int completed = 0;
	int retval;
	while (completed < data->l) {
		retval = XfireSend(s, data, completed);
		if (retval < 1) return retval;
		completed += retval;
	}
	return completed;
}

DLLEXPORT(int) XfRecvAll(int s, non0string **data, unsigned char *buffer, size_t *buflength) {
	int retval;
	do {
		retval = XfireRecv(s, data, buffer, buflength);
		if (retval < 1) return retval;
	} while (*data == NULL);
	return retval;
}


/*
 * XfirePHo_Init (unsigned int pid, unsigned int attrs, int shortnames)
 *   Initializes network packet creation and sets AddAttribute's next index to be 0
 * 
 * Parameters:
 *   pid         Packet ID number
 *   attrs       Number of attributes the packet will hold
 *   shortnames  If the packet uses short attribute names or not
 * 
 * Returns:
 *   pointer to allocated and initialized XfirePacket structure
 */
DLLEXPORT(struct XfirePacket*) XfirePHo_Init(unsigned int pid, unsigned int attrs, int shortnames) {
	struct XfirePacket *p = XfirePacketCreate(attrs, shortnames);
	p->pid = pid;
	return p;
}

/*
 * XfirePHo_AddAttribute
 *   Adds an attribute to packet p which was started
 *   recently with XfirePHo_Init
 * 
 * Parameters:
 *   p      packet structure into which to add the attribute
 *   name   attribute name
 *   aid    attribute type, one of the XFP_TYPE_ defines
 *   data   attribute data (always a pointer, even for int)
 * 
 * Returns:
 *   0 on error (trying to add too many attributes, or unknown aid)
 *   1 on success
 */
DLLEXPORT(int) XfirePHo_AddAttribute(struct XfirePacket *p, const unsigned char *name,
		unsigned int aid, void *data) {
	non0string *attrname;
	struct XfirePattr *a = NULL;
	
	if (!p) {
		return 0;
	}
	if (p->length >= p->attrs) {
		return 0;
	}
	
	/* Create name for attribute, change length and string as appropriate */
	attrname = char2non(name);
	
	switch (aid) {
		case XFP_TYPE_INT:
			a = XfireAttributeInt(attrname, *(unsigned int *)data);
			break;
		case XFP_TYPE_LONG:
			a = XfireAttributeLong(attrname, *(unsigned long long int *)data);
			break;
		case XFP_TYPE_BOOLEAN:
			a = XfireAttributeBool(attrname, *(unsigned int *)data);
			break;
		case XFP_TYPE_STRING:
			a = XfireAttributeString(attrname, (non0string*)data);
			break;
		case XFP_TYPE_SID:
			a = XfireAttributeSid(attrname, (non0string*)data);
			break;
		case XFP_TYPE_GCID:
			a = XfireAttributeGcid(attrname, (non0string*)data);
			break;
		case XFP_TYPE_LIST:
			a = XfireAttributeList(attrname, (struct XfirePattr4*)data);
			break;
		case XFP_TYPE_PACKET:
		case XFP_TYPE_NPACKET:
			a = XfireAttributePacket(attrname, (struct XfirePacket*)data);
			break;
	}
	if (!a) {
		n0sfree(attrname);
		return 0;
	}
	/* Create attribute and set it */
	XfirePacketSetAttribute(p, p->length++, a);
	return 1;
}

/*
 * XfirePHo_HashPassword
 *   Calculates the hash for username/password pair
 * 
 * Parameters:
 *   username   The Xfire username to log in
 *   password   The password for user to log in
 * 
 * Returns:
 *   Hashed password (remember to free after use)
 *   NULL if username or password NULL
 */
DLLEXPORT(non0string*) XfirePHo_HashPassword(non0string *username,
		non0string *password) {
	non0string *intermstring, *intermhash;
		
	if (!username) return NULL;
	if (!password) return NULL;
	
	intermstring = n0salloc(username->l+password->l+13);
	memcpy(intermstring->s, username->s, username->l);
	memcpy(intermstring->s+username->l, password->s, password->l);
	memcpy(intermstring->s+username->l+password->l, "UltimateArena", 13);
	
	intermhash = n0sha1(intermstring);
	n0sfree(intermstring);
	
	return intermhash;
}

/*
 * XfirePHo_HashWithSalt
 *   Calculates the hash for login
 * 
 * Parameters:
 *   pwhash   The username/password hash from XfirePHo_HashPassword
 *   salt     The login salt from the server
 * 
 * Returns:
 *   Salt-encrypted password hash
 *   NULL if either parameter NULL
 */
DLLEXPORT(non0string*) XfirePHo_HashWithSalt(non0string *pwhash,
		non0string *salt) {
	non0string *intermstring, *hash;
		
	if (!pwhash) return NULL;
	if (!salt) return NULL;
	
	intermstring = n0salloc(pwhash->l + salt->l);
	memcpy(intermstring->s, pwhash->s, pwhash->l);
	memcpy(intermstring->s+pwhash->l, salt->s, salt->l);
	
	hash = n0sha1(intermstring);
	n0sfree(intermstring);
	
	return hash;
}

/*
 * XfirePHo_HashPasswordWithSalt
 *   Calculates the hashed password used for login
 * 
 * Parameters:
 *   username   The Xfire username to log in
 *   password   The password for user to log in
 *   salt       The login salt received from the server
 * 
 * Returns:
 *   Hashed password (remember to free after use)
 *   NULL if any parameter NULL
 */
DLLEXPORT(non0string*) XfirePHo_HashPasswordWithSalt(non0string *username,
		non0string *password, non0string *salt) {
	non0string *intermhash, *hash;
	
	intermhash = XfirePHo_HashPassword(username, password);
	
	if (!intermhash) return NULL;
	if (!salt) return NULL;
	
	hash = XfirePHo_HashWithSalt(intermhash, salt);
	n0sfree(intermhash);
	
	return hash;
}
